//****************************
//** CSoundHigh
//**
//** A wave mixing class
//**
//****************************

#include "stdafx.h"
#include "CSoundHigh.h"


HWAVEOUT		m_hwo;
WAVEHDR			m_waveHeader[NUM_BUFFERS];
void* 			m_pWaveBuffer[NUM_BUFFERS];		
int				m_nBuffersSent;	
int				m_nNextBuffer;	
ULONG			m_nBufferSamples;					// pWaveBuffer size in samples
int				m_nTimerInterval;
UINT			m_idTimer;

int				m_bSounds[MAX_SOUNDS];			// array of which effects currently playing
SOUND			m_Sounds[MAX_SOUNDS];			// array of effect sounds	
SOUND			m_Loops[MAX_LOOPS];				// array of music loops
int				m_nSoundsLoaded, m_nLoopsLoaded;	// how many of each loaded
int				m_iCurrentLoop, m_iNextLoop;		// indexes into loop array
DWORD			m_iCurrentMusicSample;			// index to current sample
unsigned char	m_nMusicVol, m_nEffectVol;


// ** forward declares for callbacks, etc.
static void CALLBACK CallbackSound(HWAVEOUT hwo,UINT uMsg,DWORD dwInstance,DWORD dwParam1,DWORD dwParam2);
static void WriteMixedSamples(short * pMixBuffer, unsigned int nSamplesToMix);
static void CALLBACK PlayTimerProc(HWND hWnd, UINT nMsg, UINT nIDEvent, DWORD dwTime);
static void PlaySome(void);



CSoundHigh::CSoundHigh() {

}

CSoundHigh::~CSoundHigh() {

}

int CSoundHigh::SoundInit() {
	int iRetVal;
	WAVEFORMATEX	waveFormat;
	WORD			nSampleSize;
	MMRESULT		mmResult;
	int				iBuffer;

	int freq = 22050;
	int nBufferMS = 200;
	nSampleSize = 1;

	nSampleSize <<= 1; nSampleSize <<= 1;

	waveFormat.wFormatTag = WAVE_FORMAT_PCM;
	waveFormat.nChannels = (smStereo == smStereo)?2:1;
	waveFormat.nSamplesPerSec = freq;
	waveFormat.nAvgBytesPerSec = freq * nSampleSize;
	waveFormat.nBlockAlign = nSampleSize;
	waveFormat.wBitsPerSample = (ss16bit == ss16bit) ? 16 : 8;
	waveFormat.cbSize = sizeof(waveFormat);

	mmResult=waveOutOpen(&m_hwo, WAVE_MAPPER, &waveFormat, (DWORD)CallbackSound, 0, CALLBACK_FUNCTION);
	if (mmResult!=MMSYSERR_NOERROR) {
		return false;
	}

	m_nBufferSamples = freq * nBufferMS / (1000 * NUM_BUFFERS);

	for (iBuffer = 0; iBuffer < NUM_BUFFERS; iBuffer++) {

		m_pWaveBuffer[iBuffer] = malloc(m_nBufferSamples * nSampleSize);

		if (m_pWaveBuffer[iBuffer] == 0) 
			return false;

		m_waveHeader[iBuffer].lpData = (char*)m_pWaveBuffer[iBuffer];
		m_waveHeader[iBuffer].dwBufferLength = m_nBufferSamples * nSampleSize;
		mmResult=waveOutPrepareHeader(m_hwo,&m_waveHeader[iBuffer],sizeof(WAVEHDR));

		if (!m_pWaveBuffer[iBuffer]||mmResult!=MMSYSERR_NOERROR)
			return false;
	}

	m_nTimerInterval = (nBufferMS / NUM_BUFFERS) / TIMERS_PER_BUFFER_TIME;

	m_nBuffersSent = m_nNextBuffer = 0;
	m_iCurrentMusicSample = 0;
	m_nSoundsLoaded = m_nLoopsLoaded = 0;
	m_nMusicVol = m_nEffectVol = 255;


	// setup timer
	m_idTimer = SetTimer(NULL, NULL, m_nTimerInterval, &PlayTimerProc);


	return iRetVal;
}

int CSoundHigh::SoundLoad(int iSoundNum, LPTSTR lpFile) {
	int iRetVal;
	WAVEFILE wavefile;

	if (iSoundNum > MAX_SOUNDS)
		return 1;

	m_Sounds[iSoundNum].pWaveFile = 
		LoadWaveFile(lpFile, &wavefile);
	if (m_Sounds[iSoundNum].pWaveFile!=NULL) {
		m_Sounds[iSoundNum].pData = (short *)wavefile.pbData;
		m_Sounds[iSoundNum].bLoaded = true;
		//wavefile.cbSize is in bytes but we're doing 16 bits
		m_Sounds[iSoundNum].nSamples = wavefile.cbSize / 2;
		m_nSoundsLoaded++;
		iRetVal = 0;
	}
	else
		iRetVal = 2;
	return iRetVal;
}

int CSoundHigh::SoundPlay(int lSoundNum) {
	int iRetVal=0
		;
	if (lSoundNum < 0 || lSoundNum > MAX_SOUNDS) return 1;

	if (m_Sounds[lSoundNum].bLoaded) {
		m_bSounds[lSoundNum] = true;
		m_Sounds[lSoundNum].nPanning = 255;
		m_Sounds[lSoundNum].iCurrentSample = 0;
	}

	return iRetVal;

}

int CSoundHigh::SoundLoadLoop(int iSoundLoopNum, LPTSTR lpFile) {
	int iRetVal;

	return iRetVal;

} 

int CSoundHigh::SoundPlayLoop(int lSoundNum) {
	int iRetVal;

	return iRetVal;
}

int CSoundHigh::SoundDisable() {
	int iRetVal;

	return iRetVal;
}


LPVOID CSoundHigh::LoadWaveFile    
  (LPWSTR         szFileName,  // Filename to use
   LPWAVEFILE    pWaveFile)   // Points to the struct to fill
{
    FILE *pFile;
    LPVOID   lpMemory;
    DWORD    dwSize;
	fpos_t	 fpSize;

    // Open da file and read da bits
    pFile = _wfopen( szFileName, _T("rb"));
    fseek (pFile, 0, SEEK_END);
	dwSize = fgetpos(pFile, &fpSize);
    fseek (pFile, 0, SEEK_SET);
    lpMemory = malloc((unsigned int)fpSize);

    fread(lpMemory, 1, (unsigned int)fpSize, pFile);
    fclose(pFile);

    // Parse it out
    if (ParseWaveMemory(lpMemory, 
                             &(pWaveFile->pwfxInfo), 
                             &(pWaveFile->pbData), 
                             &(pWaveFile->cbSize)))
        {
        return lpMemory;  // OK
        }

    return NULL; // Failure
}

//////////////////////////////////////////////////////////////////
//
// wave_ParseWaveMemory
//   Parses a chunk of memory into the header and samples.
//   This is done by looking for the "fmt " and "data"
//   fields in the memory.
//
//////////////////////////////////////////////////////////////////

BOOL CSoundHigh::ParseWaveMemory

  (LPVOID          lpChunkOfMemory, // Points to raw ram
   LPWAVEFORMATEX  *lplpWaveHeader, // Points to pointer to header
   LPBYTE          *lplpWaveSamples,// Points to pointer to samples
   LPDWORD         lpcbWaveSize)    // Points to size
{
    LPDWORD pdw;
    LPDWORD pdwEnd;
    DWORD   dwRiff;
    DWORD   dwType;
    DWORD   dwLength;

    // Set defaults to NULL or zero
    if (lplpWaveHeader)
        *lplpWaveHeader = NULL;

    if (lplpWaveSamples)
        *lplpWaveSamples = NULL;

    if (lpcbWaveSize)
        *lpcbWaveSize = 0;

    // Set up DWORD pointers to the start of the chunk
    // of memory.
    pdw = (DWORD *)lpChunkOfMemory;

    // Get the type and length of the chunk of memory
    dwRiff = *pdw++;
    dwLength = *pdw++;
    dwType = *pdw++;

    // Using the mmioFOURCC macro (part of Windows SDK), ensure
    // that this is a RIFF WAVE chunk of memory
    if (dwRiff != mmioFOURCC('R', 'I', 'F', 'F'))
      return FALSE;      // not even RIFF

    if (dwType != mmioFOURCC('W', 'A', 'V', 'E'))
      return FALSE;      // not a WAV

    // Find the pointer to the end of the chunk of memory
    pdwEnd = (DWORD *)((BYTE *)pdw + dwLength-4);  //*ACK* was 4

    // Run through the bytes looking for the tags
    while (pdw < pdwEnd)
      {
      dwType   = *pdw++;
      dwLength = *pdw++;

      switch (dwType)
        {
        // Found the format  part
        case mmioFOURCC('f', 'm', 't', ' '):

          if (lplpWaveHeader && !*lplpWaveHeader)
            {
            if (dwLength < sizeof(WAVEFORMAT))
              return FALSE; // something's wrong! Not a WAV

            // Set the lplpWaveHeader to point to this part of
            // the memory chunk
            *lplpWaveHeader = (LPWAVEFORMATEX)pdw;

            // Check to see if the other two items have been
            // filled out yet (the bits and the size of the
            // bits). If so, then this chunk of memory has
            // been parsed out and we can exit
            if ((!lplpWaveSamples || *lplpWaveSamples) &&
                (!lpcbWaveSize || *lpcbWaveSize))
              {
              return TRUE;
              }
            }
          break;

        // Found the samples
        case mmioFOURCC('d', 'a', 't', 'a'):

          if ((lplpWaveSamples && !*lplpWaveSamples) ||
              (lpcbWaveSize && !*lpcbWaveSize))
            {
            // Point the samples pointer to this part of the
            // chunk of memory.
            if (lplpWaveSamples) *lplpWaveSamples = (LPBYTE)pdw;

            // Set the size of the wave
            if (lpcbWaveSize)    *lpcbWaveSize = dwLength;

            // Make sure we have our header pointer set up.
            // If we do, we can exit
            if (!lplpWaveHeader || *lplpWaveHeader)
              return TRUE;
            }
          break;

        } // End case

      // Move the pointer through the chunk of memory
      pdw = (DWORD *)((BYTE *)pdw + ((dwLength+1)&~1));
      }

  // Failed! If we made it here, we did not get all the peices
  // of the wave
  return FALSE;
}


// ====================================================================
// internals

static void CALLBACK PlayTimerProc(HWND hWnd, UINT nMsg, UINT nIDEvent, DWORD dwTime)
{
	PlaySome();
}

static void CALLBACK CallbackSound(HWAVEOUT hwo,UINT uMsg,DWORD dwInstance,DWORD dwParam1,DWORD dwParam2)
{
	if (uMsg == WOM_DONE) 
		m_nBuffersSent--;
}
  
static void PlaySome(void)
{
	if (m_hwo) {
		while (m_nBuffersSent < NUM_BUFFERS) {
			WriteMixedSamples((short*)m_pWaveBuffer[m_nNextBuffer], m_nBufferSamples);
			waveOutWrite(m_hwo, &m_waveHeader[m_nNextBuffer], sizeof(WAVEHDR));
			m_nNextBuffer++;
			if (m_nNextBuffer>=NUM_BUFFERS) 
				m_nNextBuffer %= NUM_BUFFERS;
			m_nBuffersSent++;
		}
	}
}

static void WriteMixedSamples(short * pMixBuffer, unsigned int nSamplesToMix) 
{
	unsigned int iSample, iEffect;
	long mixedSampleLeft, mixedSampleRight;
	long lLeftAdd, lRightAdd;

	for (iSample = 0; iSample < nSamplesToMix; iSample++) {
		if (m_Loops[m_iCurrentLoop].bLoaded) {
			mixedSampleLeft = m_Loops[m_iCurrentLoop].pData[m_iCurrentMusicSample * 2] *
				//times volume div 256
				m_nMusicVol / 256;
			mixedSampleRight = m_Loops[m_iCurrentLoop].pData[m_iCurrentMusicSample * 2 + 1] *
				//times volume div 256
				m_nMusicVol / 256;

			m_iCurrentMusicSample++;
			// if we've reached the end of the music loop
			if (m_iCurrentMusicSample == m_Loops[m_iCurrentLoop].nSamples) {
				m_iCurrentMusicSample = 0;
				m_iCurrentLoop = m_iNextLoop;
			}
		}
		else
		{
			mixedSampleLeft = 0;
			mixedSampleRight = 0;
		}

		//mix all currently playing effects with the music stream
		for (iEffect = 0; iEffect < MAX_SOUNDS; iEffect++) {
			//if we're zapping, and effect valid, mix some data
			if (m_bSounds[iEffect] && m_Sounds[iEffect].bLoaded) {
				//if still data this effect, go wild, add it in
				if (m_Sounds[iEffect].iCurrentSample < m_Sounds[iEffect].nSamples) {
					lLeftAdd = 
						//sample
						m_Sounds[iEffect].pData[m_Sounds[iEffect].iCurrentSample] *	
							//times volume (0-255)
//							effect[iEffect].nVolume * 
							m_nEffectVol *
							//times panning value (0 = full left, 255 = full right)
							(255 - m_Sounds[iEffect].nPanning) / 
							//div 256*256 because of above 0-255 factors
							65536 / 
							//div overall factor
							OVERALL_SCALE_FACTOR;
					mixedSampleLeft += (short)lLeftAdd;
					lRightAdd = 
						//sample
						m_Sounds[iEffect].pData[m_Sounds[iEffect].iCurrentSample] *	
							//times volume (0-255)
//							effect[iEffect].nVolume * 
							m_nEffectVol *
							//times panning value (0 = full left, 255 = full right)
							m_Sounds[iEffect].nPanning / 
							//div 256*256 because of above 0-255 factors
							65536 / 
							//div overall factor
							OVERALL_SCALE_FACTOR;
					mixedSampleRight += (short)lRightAdd;
				}
				//otherwise stop zapping already
				else
					m_bSounds[iEffect] = false;
				m_Sounds[iEffect].iCurrentSample++;
			}
		
		pMixBuffer[iSample * 2] = (short)mixedSampleLeft;
		pMixBuffer[iSample * 2 + 1] = (short)mixedSampleRight;

		}

	}
}


